<!DOCTYPE html><html lang="zh-Hans"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="深入理解iOS中的RunLoop"><meta name="keywords" content="ObjC,RunLoop"><meta name="author" content="布多"><meta name="copyright" content="布多"><title>深入理解iOS中的RunLoop | 布多的博客</title><link rel="shortcut icon" href="/melody-favicon.ico"><link rel="stylesheet" href="/css/index.css?version=1.9.1"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.9.1"><meta name="format-detection" content="telephone=no"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"><link rel="dns-prefetch" href="https://hm.baidu.com"><script>var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?f0d94d3f99211c3546490e469f6ecbd8";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();</script><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><script src="https://v1.hitokoto.cn/?encode=js&amp;charset=utf-8&amp;select=.footer_custom_text" defer></script><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容:${query}"}},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  hexoVersion: '6.3.0'
} </script><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="布多的博客" type="application/atom+xml">
</head><body><canvas class="fireworks"></canvas><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="切换文章详情">切换站点概览</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">目录</div><div class="sidebar-toc__progress"><span class="progress-notice">你已经读了</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#RunLoop%E6%98%AF%E4%BB%80%E4%B9%88"><span class="toc-number">1.</span> <span class="toc-text">RunLoop是什么</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#RunLoop%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="toc-number">2.</span> <span class="toc-text">RunLoop源码分析</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#RunLoopMode"><span class="toc-number">2.1.</span> <span class="toc-text">RunLoopMode</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#CFRunLoopSourceRef"><span class="toc-number">2.2.</span> <span class="toc-text">CFRunLoopSourceRef</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#CFRunLoopTimerRef"><span class="toc-number">2.3.</span> <span class="toc-text">CFRunLoopTimerRef</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#CFRunLoopObserverRef"><span class="toc-number">2.4.</span> <span class="toc-text">CFRunLoopObserverRef</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#RunLoop%E4%B8%8E%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%85%B3%E7%B3%BB"><span class="toc-number">3.</span> <span class="toc-text">RunLoop与线程的关系</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#RunLoop%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="toc-number">4.</span> <span class="toc-text">RunLoop的作用</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%B3%BB%E7%BB%9F%E7%94%A8-RunLoop-%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="toc-number">4.1.</span> <span class="toc-text">系统用 RunLoop 做了什么？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%88%91%E4%BB%AC%E8%83%BD%E7%94%A8-RunLoop-%E5%81%9A%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="toc-number">4.2.</span> <span class="toc-text">我们能用 RunLoop 做什么？</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#RunLoop-%E7%9B%B8%E5%85%B3%E9%9D%A2%E8%AF%95%E9%A2%98"><span class="toc-number">5.</span> <span class="toc-text">RunLoop 相关面试题</span></a></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="images/avatar.jpg"></div><div class="author-info__name text-center">布多</div><div class="author-info__description text-center">前进！前进！！不择手段地前进！！！</div><div class="follow-button"><a target="_blank" rel="noopener" href="https://github.com/internetwei">Follow Me</a></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">文章</span><span class="pull-right">9</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">标签</span><span class="pull-right">9</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">分类</span><span class="pull-right">4</span></a></div><hr><div class="author-info-links"><div class="author-info-links__title text-center">友链</div><a class="author-info-links__name text-center" target="_blank" rel="noopener" href="https://www.coderqi.com/">齐小胖之家</a></div></div></div><div id="content-outer"><div id="top-container" style="background-image: url(images/backgroundImage.jpg)"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">布多的博客</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus">   <a class="site-page" href="/">首页</a><a class="site-page" href="/tags">标签</a><a class="site-page" href="/categories">分类</a><a class="site-page" href="/archives">归档</a><a class="site-page" href="/about">关于</a></span><span class="pull-right"><a class="site-page social-icon search"><i class="fa fa-search"></i><span> 搜索</span></a></span></div><div id="post-info"><div id="post-title">深入理解iOS中的RunLoop</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2024-04-28</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/iOS/">iOS</a><div class="post-meta-wordcount"><span>字数总计: </span><span class="word-count">4.7k</span><span class="post-meta__separator">|</span><span>阅读时长: 16 分钟</span></div></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><blockquote>
<p>由 布多(budo) 发布于 2024-04-28</p>
</blockquote>
<h2 id="RunLoop是什么"><a href="#RunLoop是什么" class="headerlink" title="RunLoop是什么"></a>RunLoop是什么</h2><p>RunLoop 是 iOS 开发中一个比较基础的概念，苹果有很多功能都是基于 RunLoop 实现的，例如线程中的自动释放池、NSTimer、系统事件响应、屏幕刷新等等。这篇文章将从源码的角度窥探 RunLoop 的内部实现细节。</p>
<p>在讲 RunLoop 之前，要先介绍一下线程。正常情况下，一个线程一次只能执行一个任务，当任务执行完线程就会退出，想要再执行任务只能再开启一个新线程。</p>
<p>如果我们想要一个机制，让线程在处理完任务后不自动退出而是一直处于待命状态，随时处理我们的任务。这种机制通常被称为 <a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Event_loop">Even Loop</a>。</p>
<p>Even Loop 在很多系统里都有实现，比如 Windows 里的消息循环，iOS 里的 RunLoop 等等。</p>
<p>iOS 中的 RunLoop 本质上是一个对象，这个对象管理了需要处理的事件和消息，线程执行了这个方法后，就会一直处于「接受消息-&gt;处理消息-&gt;等待消息」的循环中，伪代码如下：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> startRunLoop() &#123;</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        var message = get_next_message();</span><br><span class="line">        process_message(message);</span><br><span class="line">    &#125; <span class="keyword">while</span> (message != quit);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="RunLoop源码分析"><a href="#RunLoop源码分析" class="headerlink" title="RunLoop源码分析"></a><span id="anchor1">RunLoop源码分析</span></h2><p>iOS 提供了两个 RunLoop 对象：NSRunLoop 和 CFRunLoopRef。其中 NSRunLoop 是基于 CFRunLoopRef 的封装，提供了面向对象的 API，CFRunLoopRef 是开源的，你可以下载 <a target="_blank" rel="noopener" href="https://github.com/apple-oss-distributions/CF">CoreFoundataion</a> 源码来查看。</p>
<p>在 CoreFoundation 里，与 RunLoop 相关的类有以下 5 个：</p>
<ul>
<li>CFRunLoopRef(<code>RunLoop</code>)</li>
<li>CFRunLoopModeRef(<code>Mode</code>)</li>
<li>CFRunLoopSourceRef(<code>Source</code>)</li>
<li>CFRunLoopTimerRef(<code>Timer</code>)</li>
<li>CFRunLoopObserverRef(<code>Observer</code>)</li>
</ul>
<p>它们的关系如图1所示：</p>
<table>
<thead>
<tr>
<th align="center">图1</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="/assets/RunLoop_0.png" alt="RunLoop内部结构图"></td>
</tr>
</tbody></table>
<p>相关代码结构精简后如下所示：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> __CFRunLoopMode *<span class="built_in">CFRunLoopModeRef</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> __CFRunLoop *<span class="built_in">CFRunLoopRef</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> __CFRunLoop &#123;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _commonModes;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _commonModeItems;</span><br><span class="line">    <span class="built_in">CFRunLoopModeRef</span> _currentMode;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _modes;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> __CFRunLoopMode &#123;</span><br><span class="line">    <span class="built_in">CFStringRef</span> _name;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _sources0;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _sources1;</span><br><span class="line">    <span class="built_in">CFMutableArrayRef</span> _observers;</span><br><span class="line">    <span class="built_in">CFMutableArrayRef</span> _timers;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>结合上面的图和代码，我们可以发现，一个 RunLoop 对象里面有若干个 Mode，其保存在变量 _modes 中，但同一时间只能运行 _modes 中的一个 mode，用变量 _currentMode 表示。其中 Mode 对象包含了需要处理的事件 Source0、Source1、Observer、Timer。</p>
<p>RunLoop 的内部逻辑大致如图2所示：</p>
<table>
<thead>
<tr>
<th align="center">图2</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="/assets/iShot_2024-03-12_19.28.33.png" alt="RunLoop内部逻辑图"></td>
</tr>
</tbody></table>
<p>RunLoop 内部核心代码整理后如下所示：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 入口函数1</span></span><br><span class="line"><span class="type">void</span> <span class="built_in">CFRunLoopRun</span>(<span class="type">void</span>) &#123;</span><br><span class="line">    int32_t result;</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        result = <span class="built_in">CFRunLoopRunSpecific</span>(<span class="built_in">CFRunLoopGetCurrent</span>(), kCFRunLoopDefaultMode, <span class="number">1.0e10</span>, <span class="literal">false</span>);</span><br><span class="line">    &#125; <span class="keyword">while</span> (kCFRunLoopRunStopped != result &amp;&amp; kCFRunLoopRunFinished != result);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 入口函数2，可以指定 Mode 和超时时间</span></span><br><span class="line"><span class="comment">// 以及 returnAfterSourceHandled（处理完事件后是否退出 RunLoop）。</span></span><br><span class="line">SInt32 <span class="built_in">CFRunLoopRunInMode</span>(<span class="built_in">CFStringRef</span> modeName, <span class="built_in">CFTimeInterval</span> seconds, Boolean returnAfterSourceHandled) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">CFRunLoopRunSpecific</span>(<span class="built_in">CFRunLoopGetCurrent</span>(), modeName, seconds, returnAfterSourceHandled);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">SInt32 <span class="built_in">CFRunLoopRunSpecific</span>(<span class="built_in">CFRunLoopRef</span> rl, <span class="built_in">CFStringRef</span> modeName, <span class="built_in">CFTimeInterval</span> seconds, Boolean returnAfterSourceHandled) &#123;    </span><br><span class="line">    <span class="comment">// 根据名称获取 RunLoopMode 对象。</span></span><br><span class="line">    <span class="comment">// 第3个参数的作用是：如果找不到对应名称的 RunLoopMode 对象是否新创建一个。</span></span><br><span class="line">    <span class="built_in">CFRunLoopModeRef</span> currentMode = __CFRunLoopFindMode(rl, modeName, <span class="literal">false</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果 RunLoopMode 里没有任何事情（没有Observer、Timer、Source），直接退出。</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">NULL</span> == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl-&gt;_currentMode)) &#123;</span><br><span class="line">	   <span class="keyword">return</span> kCFRunLoopRunFinished;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    int32_t result = kCFRunLoopRunFinished;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 通知 Observer，即将进入 RunLoop。</span></span><br><span class="line">	<span class="keyword">if</span> (currentMode-&gt;_observerMask &amp; kCFRunLoopEntry ) &#123;</span><br><span class="line">	   __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);</span><br><span class="line">	</span><br><span class="line">    <span class="comment">// 通知 Observer，即将退出 RunLoop。</span></span><br><span class="line">	<span class="keyword">if</span> (currentMode-&gt;_observerMask &amp; kCFRunLoopExit ) &#123;</span><br><span class="line">	   __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// RunLoop 核心函数。</span></span><br><span class="line"><span class="keyword">static</span> int32_t __CFRunLoopRun(<span class="built_in">CFRunLoopRef</span> rl, <span class="built_in">CFRunLoopModeRef</span> rlm, <span class="built_in">CFTimeInterval</span> seconds, Boolean stopAfterHandle, <span class="built_in">CFRunLoopModeRef</span> previousMode) &#123;</span><br><span class="line">    int32_t retVal = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        <span class="comment">// 通知 Observer，即将处理 Timer。</span></span><br><span class="line">        <span class="keyword">if</span> (rlm-&gt;_observerMask &amp; kCFRunLoopBeforeTimers) &#123;</span><br><span class="line">            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 通知 Observer，即将处理 Source。</span></span><br><span class="line">        <span class="keyword">if</span> (rlm-&gt;_observerMask &amp; kCFRunLoopBeforeSources) &#123;</span><br><span class="line">            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 处理加入的 Block。</span></span><br><span class="line">        __CFRunLoopDoBlocks(rl, rlm);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 处理 Source0。</span></span><br><span class="line">        <span class="keyword">if</span> (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) &#123;</span><br><span class="line">            <span class="comment">// 处理加入的 Block。</span></span><br><span class="line">            __CFRunLoopDoBlocks(rl, rlm);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果有 Source1 事件要处理，跳转到 handle_msg 进行处理。</span></span><br><span class="line">        <span class="keyword">if</span> (__CFRunLoopServiceMachPort(...)) &#123;</span><br><span class="line">            <span class="keyword">goto</span> handle_msg;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 通知 Observer，RunLoop 即将进入休眠</span></span><br><span class="line">        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 让当前线程休眠。</span></span><br><span class="line">        __CFRunLoopServiceMachPort(..., TIMEOUT_INFINITY, ...);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 通知 Observer，RunLoop 被唤醒了。</span></span><br><span class="line">        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);</span><br><span class="line">        </span><br><span class="line">        handle_msg:;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果有一个 Timer 的时间到了，就触发这个 Timer 回调。</span></span><br><span class="line">        <span class="keyword">if</span> (rlm-&gt;_timerPort != MACH_PORT_NULL &amp;&amp; livePort == rlm-&gt;_timerPort) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) &#123;</span><br><span class="line">                __CFArmNextTimerInMode(rlm, rl);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果有一个 GCD 切换到主线程的操作，就执行这个操作。</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (livePort == dispatchPort) &#123;</span><br><span class="line">            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 处理 Source1 事件。</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg-&gt;msgh_size, &amp;reply);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 处理加入的 Block。</span></span><br><span class="line">        __CFRunLoopDoBlocks(rl, rlm);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 进入 RunLoop 时参数说处理完事件就返回。（即 stopAfterHandle 为 true）</span></span><br><span class="line">        <span class="keyword">if</span> (sourceHandledThisLoop &amp;&amp; stopAfterHandle) &#123;</span><br><span class="line">            retVal = kCFRunLoopRunHandledSource;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// RunLoop 超时了。</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (timeout_context-&gt;termTSR &lt; mach_absolute_time()) &#123;</span><br><span class="line">            retVal = kCFRunLoopRunTimedOut;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 被外部调用者强行停止了。</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (__CFRunLoopIsStopped(rl) || rlm-&gt;_stopped) &#123;</span><br><span class="line">            retVal = kCFRunLoopRunStopped;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Mode 里面没有需要处理的事件了。</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) &#123;</span><br><span class="line">            retVal = kCFRunLoopRunFinished;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果没有超时、没有被强行停止、Mode 里的事件不为空，继续 RunLoop。       </span></span><br><span class="line">    &#125; <span class="keyword">while</span> (<span class="number">0</span> == retVal);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> retVal;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从源码中我们可以发现，RunLoop 本质上就是一个函数，这个函数的内部有一个 do while 循环，当线程进入 RunLoop 后，就会一直停留在这个循环里，直到超时或手动停止等情况，才会结束退出。</p>
<h3 id="RunLoopMode"><a href="#RunLoopMode" class="headerlink" title="RunLoopMode"></a><span id="anchor5">RunLoopMode</span></h3><p>RunLoopMode 是 RunLoop 的核心，RunLoopMode 里包含了 RunLoop 需要处理的 Source、Timer 和 Observer。<br>一个 RunLoop 里面可能有多个 RunLoopMode，但同一时间只能运行其中的一个 Mode，如果想要运行其它 Mode，需要退出当前 Mode，然后指定那个 Mode 重新进入 RunLoop。</p>
<p>之所以要区分不同的 Mode，是为了分隔不同 Mode 中的事件，让其相互独立，互不干扰。</p>
<p>RunLoopMode 本质上是一个 CFRunLoopModeRef 对象，这个对象中包含了 Source、Observer、Timer 这几个属性。我们不能直接创建和使用 CFRunLoopModeRef，只能通过字符串标识来间接的操作和获取指定的 CFRunLoopModeRef 对象。我们可以自定义 RunLoopMode，不过一般没有这个必要。苹果为我们提供了一些常用的Mode，分别是以下三个：</p>
<ul>
<li>kCFRunLoopDefaultMode</li>
<li>UITrackingRunLoopMode</li>
<li>kCFRunLoopCommonModes</li>
</ul>
<p>除了这些 Mode，系统内部还有其它 Mode，不过我们一般都用不到。</p>
<p>需要特别说明的是，kCFRunLoopCommonModes 并不是一个真正的 Mode，它只是一个占位符，用来表示所有被标记了「Common」属性的 Mode，kCFRunLoopDefaultMode 和 UITrackingRunLoopMode 都被标记了「Common」属性；你可以使用以下函数手动将某个 Mode 标记为「Common」属性。</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="built_in">CFRunLoopAddCommonMode</span>(<span class="built_in">CFRunLoopRef</span>, <span class="built_in">CFRunLoopMode</span>)</span><br></pre></td></tr></table></figure>

<p>kCFRunLoopCommonModes 的作用主要是为了减少代码量，下面我将用一个例子来说明它的作用：<br>假设当前线程中有 10 个 Mode（Mode1……Mode10），当我创建了一个定时器 timer，如果我想让这个定时器在这 10 个 Mode 下都能运行的话，我需要写出如下代码：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model1];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model2];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model3];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model4];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model5];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model6];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model7];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model8];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model9];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:model10];</span><br></pre></td></tr></table></figure>

<p>如果有了 kCFRunLoopCommonModes 的话，我们只需要保证这些 Mode 都被标记了「Common」属性，然后只需要一行代码即可实现上面同样的效果：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:kCFRunLoopCommonModes];</span><br></pre></td></tr></table></figure>

<p>下面我将向你解释苹果是怎么实现的：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> __CFRunLoop &#123;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _commonModes;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _commonModeItems;</span><br><span class="line">    <span class="built_in">CFRunLoopModeRef</span> _currentMode;</span><br><span class="line">    <span class="built_in">CFMutableSetRef</span> _modes;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>当某个 Mode 被标记为「Common」属性后，系统会自动将这个 Mode 添加到 RunLoop 的变量_commonModes 中。（<code>UITrackingRunLoopMode 和 kCFRunLoopDefaultMode 都被标记了这个属性</code>）当你将 timer 添加到 kCFRunLoopCommonModes 时，系统不会将这个 timer 添加到某个具体的 Mode 中，而是添加到属性 _commonModeItems 中，每次切换 Mode 时，系统会看一下当前 Mode 是否有「Common」属性（<code>看一下 _commonModes 是否包含当前 Mode</code>），如果有的话，系统会自动将变量 _commonModeItems 中的事件全部添加到当前 Mode 中去。</p>
<p>我们经常在 APP 中写下如下代码，用来解决定时器在页面滚动时失效的问题。</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:<span class="built_in">NSRunLoopCommonModes</span>];</span><br></pre></td></tr></table></figure>

<p>现在你应该明白这背后发生了什么。其实下面两行代码也能解决同样的问题：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:<span class="built_in">NSDefaultRunLoopMode</span>];</span><br><span class="line">[[<span class="built_in">NSRunLoop</span> currentRunLoop] addTimer:timer forMode:<span class="built_in">UITrackingRunLoopMode</span>];</span><br></pre></td></tr></table></figure>

<h3 id="CFRunLoopSourceRef"><a href="#CFRunLoopSourceRef" class="headerlink" title="CFRunLoopSourceRef"></a>CFRunLoopSourceRef</h3><p>Source有两个版本：Source0 和 Source1。</p>
<p>Source0 表示应用层的事件，例如 APP 内部的事件，我们写的函数、方法都属于 Source0。Source0 只有一个回调，它不能主动触发事件，需要先调用 <code>CFRunLoopSourceSignal(source)</code> 将这个 Source 标记为待处理，然后调用 <code>CFRunLoopWakeUp(runLoop)</code> 唤醒 RunLoop，让其处理这个事件。</p>
<p>Source1 表示系统层事件，一般用于系统内核或其它线程相互发送消息，例如当用户点击了手机屏幕时，系统就会生成一个 Source1 事件，Source1 有一个 mach_port 和一个回调，它可以主动唤醒休眠中的 RunLoop。</p>
<h3 id="CFRunLoopTimerRef"><a href="#CFRunLoopTimerRef" class="headerlink" title="CFRunLoopTimerRef"></a>CFRunLoopTimerRef</h3><p>CFRunLoopTimerRef 是一个基于时间的触发器，它和 NSTimer 是免费桥接的，当它被加入到 RunLoop 时，RunLoop 会在对应的时间点上注册一个回调，当时间点到时，RunLoop 会被唤醒执行那个回调。但是这个回调的时间点不是那么的精确，如果 RunLoop 当前正在执行一个耗时的任务，就可能会跳过那个定时器。</p>
<p>出于性能考虑，NSTimer 有一个实例属性 tolerance，它的默认值是 0，你可以通过修改它来影响 NSTimer 的回调精度。假设定时器本来会在 10:10:00 回调，如果 tolerance 的值是 2 的话，那么系统就会在 10:10:00~10:10:02 之间的任意一个时间点回调，这样做的好处是，系统可以将定时器事件和其它事件合并成一次处理，有利于系统优化性能和节省电量。</p>
<h3 id="CFRunLoopObserverRef"><a href="#CFRunLoopObserverRef" class="headerlink" title="CFRunLoopObserverRef"></a>CFRunLoopObserverRef</h3><p>CFRunLoopObserverRef 是一个观察者，每个 Observer 都包含一个回调，当 RunLoop 的状态发生变化时，观察者就能通过回调接收到这个变化，可以观察的状态有如下6个：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="built_in">CF_OPTIONS</span>(<span class="built_in">CFOptionFlags</span>, <span class="built_in">CFRunLoopActivity</span>) &#123;</span><br><span class="line">    kCFRunLoopEntry = (<span class="number">1</span>UL &lt;&lt; <span class="number">0</span>),           <span class="comment">// 即将进入 Loop</span></span><br><span class="line">    kCFRunLoopBeforeTimers = (<span class="number">1</span>UL &lt;&lt; <span class="number">1</span>),    <span class="comment">// 即将处理 Timer</span></span><br><span class="line">    kCFRunLoopBeforeSources = (<span class="number">1</span>UL &lt;&lt; <span class="number">2</span>),   <span class="comment">// 即将处理 Source</span></span><br><span class="line">    kCFRunLoopBeforeWaiting = (<span class="number">1</span>UL &lt;&lt; <span class="number">5</span>),   <span class="comment">// 即将进入休眠</span></span><br><span class="line">    kCFRunLoopAfterWaiting = (<span class="number">1</span>UL &lt;&lt; <span class="number">6</span>),    <span class="comment">// 刚从休眠中唤醒</span></span><br><span class="line">    kCFRunLoopExit = (<span class="number">1</span>UL &lt;&lt; <span class="number">7</span>),            <span class="comment">// 即将退出 Loop</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="RunLoop与线程的关系"><a href="#RunLoop与线程的关系" class="headerlink" title="RunLoop与线程的关系"></a><span id="anchor3">RunLoop与线程的关系</span></h2><p>苹果不允许直接创建 RunLoop 对象，它只提供了两个用于获取 RunLoop 对象的函数：<code>CFRunLoopGetMain</code> 和 <code>CFRunLoopGetCurrent</code>，所以我们只能获取当前线程的 RunLoop 对象（<code>主线程的 RunLoop 除外</code>）。</p>
<p>相关代码整理后如下所示：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line"><span class="built_in">CFRunLoopRef</span> <span class="built_in">CFRunLoopGetCurrent</span>(<span class="type">void</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> _CFRunLoopGet0(pthread_self());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">CFRunLoopRef</span> <span class="built_in">CFRunLoopGetMain</span>(<span class="type">void</span>) &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="built_in">CFRunLoopRef</span> __main = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np());</span><br><span class="line">    <span class="keyword">return</span> __main;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="built_in">CFMutableDictionaryRef</span> __CFRunLoops = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">CFRunLoopRef</span> _CFRunLoopGet0(pthread_t t) &#123;    </span><br><span class="line">    <span class="comment">// 第一次进入时自动创建一个全局字典，并为主线程创建一个 RunLoop 对象。</span></span><br><span class="line">    <span class="keyword">if</span> (!__CFRunLoops) &#123;</span><br><span class="line">        <span class="built_in">CFMutableDictionaryRef</span> dict = <span class="built_in">CFDictionaryCreateMutable</span>(kCFAllocatorSystemDefault, <span class="number">0</span>, <span class="literal">NULL</span>, &amp;kCFTypeDictionaryValueCallBacks);</span><br><span class="line">        <span class="built_in">CFRunLoopRef</span> mainLoop = __CFRunLoopCreate(pthread_main_thread_np());</span><br><span class="line">        <span class="built_in">CFDictionarySetValue</span>(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);</span><br><span class="line">        <span class="keyword">if</span> (!OSAtomicCompareAndSwapPtrBarrier(<span class="literal">NULL</span>, dict, (<span class="type">void</span> * <span class="keyword">volatile</span> *)&amp;__CFRunLoops)) &#123;</span><br><span class="line">            <span class="built_in">CFRelease</span>(dict);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">CFRelease</span>(mainLoop);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 从全局字典获取指定 RunLoop 对象。</span></span><br><span class="line">    <span class="built_in">CFRunLoopRef</span> loop = (<span class="built_in">CFRunLoopRef</span>)<span class="built_in">CFDictionaryGetValue</span>(__CFRunLoops, pthreadPointer(t));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果获取不到的话就创建一个 RunLoop 对象，然后将它和线程一一保存到全局字典中。</span></span><br><span class="line">    <span class="keyword">if</span> (!loop) &#123;</span><br><span class="line">        <span class="built_in">CFRunLoopRef</span> newLoop = __CFRunLoopCreate(t);</span><br><span class="line">        loop = (<span class="built_in">CFRunLoopRef</span>)<span class="built_in">CFDictionaryGetValue</span>(__CFRunLoops, pthreadPointer(t));</span><br><span class="line">        <span class="keyword">if</span> (!loop) &#123;</span><br><span class="line">            <span class="built_in">CFDictionarySetValue</span>(__CFRunLoops, pthreadPointer(t), newLoop);</span><br><span class="line">            loop = newLoop;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">CFRelease</span>(newLoop);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 注册一个回调，当线程销毁时，顺便销毁其对应的 RunLoop 对象。</span></span><br><span class="line">    <span class="keyword">if</span> (pthread_equal(t, pthread_self())) &#123;</span><br><span class="line">        _CFSetTSD(__CFTSDKeyRunLoopCntr, (<span class="type">void</span> *)(PTHREAD_DESTRUCTOR_ITERATIONS<span class="number">-1</span>), (<span class="type">void</span> (*)(<span class="type">void</span> *))__CFFinalizeRunLoop);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> loop;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从上面的代码可以看出，线程和 RunLoop 的关系是一一对应的，其关系保存在一个全局字典里，字典的 key 是线程，value 是对应的 RunLoop 对象，在线程结束后会自动释放与之对应的 RunLoop 对象。</p>
<p>其中主线程的 RunLoop 对象在系统启动后会自动创建，子线程默认没有 RunLoop 对象，当你第一次获取时，系统会懒加载一个 RunLoop 对象，并将它与当前线程一一绑定。</p>
<h2 id="RunLoop的作用"><a href="#RunLoop的作用" class="headerlink" title="RunLoop的作用"></a>RunLoop的作用</h2><h3 id="系统用-RunLoop-做了什么？"><a href="#系统用-RunLoop-做了什么？" class="headerlink" title="系统用 RunLoop 做了什么？"></a>系统用 RunLoop 做了什么？</h3><p><strong><span id="anchor2">1. AutoreleasePool 和 RunLoop 的关系</span></strong><br>首先，需要声明的是，AutoreleasePool 的实现跟 RunLoop 没有任何关系。有很多面试官会问类似 “AutoreleasePool 和 RunLoop 有什么关系” 的问题，但实际上他们想问的是，被 __autoreleasing 修饰的对象在什么时候释放？例如以下代码，对象 obj 是什么时候释放的？</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">- (<span class="type">void</span>)viewDidLoad &#123;</span><br><span class="line">    [<span class="variable language_">super</span> viewDidLoad];</span><br><span class="line">    __autoreleasing <span class="built_in">NSObject</span> *obj = [[<span class="built_in">NSObject</span> alloc] init];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实际上，被 __autoreleasing 修饰的对象可能有 3 种不同的释放时机：</p>
<p>a. 第一种情况是直接被 @autoreleasepool{} 包裹起来，例如以下代码，这种情况下，对象 obj 的释放时机是在 @autoreleasepool{} 结束时自动释放。</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">- (<span class="type">void</span>)viewDidLoad &#123;</span><br><span class="line">    [<span class="variable language_">super</span> viewDidLoad];</span><br><span class="line">    <span class="keyword">@autoreleasepool</span> &#123;    </span><br><span class="line">        __autoreleasing <span class="built_in">NSObject</span> *obj = [[<span class="built_in">NSObject</span> alloc] init];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 当代码执行到这里的时候，对象 obj 就已经被释放了。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>b. 第二种情况是线程中没有 RunLoop 对象，例如在子线程中使用 __autoreleasing 修饰对象，例如以下代码，对象 obj 会在当前线程销毁时自动释放。</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">- (<span class="type">void</span>)viewDidLoad &#123;</span><br><span class="line">    [<span class="variable language_">super</span> viewDidLoad];</span><br><span class="line">    <span class="built_in">dispatch_async</span>(dispatch_get_global_queue(<span class="number">0</span>, <span class="number">0</span>), ^&#123;</span><br><span class="line">        __autoreleasing <span class="built_in">NSObject</span> *obj = [[<span class="built_in">NSObject</span> alloc] init];</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>c. 第三种情况也是最复杂的情况，当线程中有 RunLoop 对象。有很多以前的博客会告诉你，系统会自动在 RunLoop 中注册观察者，分别是进入RunLoop、RunLoop 休眠、RunLoop 退出，然后在回调中处理相关逻辑。但这个答案目前已经失效了，我在调试时发现，苹果并不会注册相关观察者，而是会直接在相关调用中插入处理逻辑。简单的说，在以前，被 __autoreleasing 修饰的对象会在 RunLoop 休眠前才统一释放，这可能会导致积累许多 __autoreleasing 对象；现在的逻辑是当 RunLoop 切换事件时就释放了。</p>
<blockquote>
<p>一些题外话：我在很多项目中发现这样一种现象，伪代码如下：</p>
<figure class="highlight objc"><table><tr><td class="code"><pre><span class="line">&gt;<span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">   <span class="keyword">@autoreleasepool</span> &#123;</span><br><span class="line">       <span class="comment">// ………</span></span><br><span class="line">   &#125;</span><br><span class="line">&gt;&#125;</span><br></pre></td></tr></table></figure>
<p>许多开发者都会在几乎所有的 for 循环中手动加上一个 @autoreleasepool{}，他们的期望是想降低内存峰值，但其实大可不必，如果 for 循环里没有被 __autoreleasing 修饰的对象，这么做并没有任何好处，只有当 for 循环里有很多 __autoreleasing 对象时，才能起到你想要的效果。</p>
<p>注：有很多对象你没有显式的用 __autoreleasing 修饰，但其实它也是 __autoreleasing 对象，因为系统&#x2F;框架在返回这个对象时，自动将它加入了自动释放池，例如这行代码 <code>[NSString stringWithFormat:@&quot;test%d&quot;, 12]</code> 系统在返回这个对象时已经把它加入了自动释放池。</p>
</blockquote>
<p><strong><span id="anchor4">2. NSTimer 和 RunLoop 的关系</span></strong><br>NSTimer 本质上就是 CFRunLoopTimerRef。当一个 NSTimer 被注册到 RunLoop 之后，RunLoop 会在重复的时间点上注册好事件，例如 11:00:00、11:00:03、11:00:06，不过，RunLoop 为了节省资源，并不会在非常准确的时间点回调。</p>
<p><strong>3. performSelecter:afterDelay: 方法和 RunLoop 的关系</strong><br>当调用 NSObject 的 <code>performSelecter:afterDelay:</code> 时，系统会在内部创建一个 Timer 并添加到当前线程的 RunLoop 中，所以，如果当前线程没有 RunLoop 的话，这个方法就会失效。</p>
<p><strong>4. 事件响应和 RunLoop 的关系</strong><br>苹果在主线程的 RunLoop 中注册了一个 Source1 用来接收系统事件，当一个硬件事件（触摸、晃动等等）发生后，系统会将这个事件通过 mach port 转发给 APP，APP 会在 Source1 的回调中收到转发过来的事件，之后，APP 会生成一个 Source0 事件在应用内部进行分发。</p>
<p><strong>5. 界面刷新和 RunLoop 的关系</strong><br>苹果在主线程的 RunLoop 中注册了一个 Observer 用来监听 RunLoop 即将进入休眠和即将退出的状态，当 UI 发生变化时，例如修改 frame、color 等等，或者手动调用 UIView&#x2F;CALayer 的 setNeedsLayout、setNeedsDisplay 方法后，这个 UI 对象会被标记为待处理，然后添加到一个全局容器中。苹果会在 Observer 的回调里处理所有的 UI 对象并执行实际的绘制和调整，然后更新 UI 界面。</p>
<h3 id="我们能用-RunLoop-做什么？"><a href="#我们能用-RunLoop-做什么？" class="headerlink" title="我们能用 RunLoop 做什么？"></a>我们能用 RunLoop 做什么？</h3><p><strong>1. APP卡顿监测</strong><br>大致原理是在主线程的 RunLoop 中注册一个 Observer，然后在回调中开启一个子线程用来判断主线程是否发生了卡顿，判断逻辑大致是，如果 RunLoop 在某一个状态下持续时间过长（一般是 60 ms），则判定为卡顿。我写了一个 Demo 实现了相关逻辑，你可以在 <a target="_blank" rel="noopener" href="https://github.com/internetWei/WXLLagMonitoring">WXLLagMonitoring</a> 这里查看。</p>
<p><strong>2. 线程保活</strong><br>原理是开启一个子线程的 RunLoop，让这个子线程执行完任务后不被释放而一直处于 RunLoop 模式中，这样我们就可以一直使用这个子线程处理一些任务，而不用每次都开启一个新的线程。我写了一个 Demo，你可以在 <a target="_blank" rel="noopener" href="https://github.com/internetWei/WXLThread">WXLThread</a> 这里查看。</p>
<p><strong>3. 性能优化</strong></p>
<ol>
<li>将多次操作合并成一次操作。例如系统的界面更新就利用了 RunLoop 的这个特性，还有框架 FDTemplateLayoutCell 也用到了这个特性。</li>
<li>监听线程的不同 Mode 来将一些耗时操作放在特定 Mode 下执行，例如监听主线程 RunLoop 的 kCFRunLoopDefaultMode 状态，当用户滑动时系统会切换到 UITrackingRunLoopMode 状态，此时不对页面上的图片进行加载和渲染，等 RunLoop 切换到 kCFRunLoopDefaultMode 时再加载和渲染图片。(<code>话说我感觉这么做有点复杂，直接监听 ScrollView 控件的滑动状态不也可以实现一样的效果么</code>)</li>
</ol>
<h2 id="RunLoop-相关面试题"><a href="#RunLoop-相关面试题" class="headerlink" title="RunLoop 相关面试题"></a>RunLoop 相关面试题</h2><ol>
<li><p>讲讲 Runloop 是什么？项目中有用到吗？</p>
<p> RunLoop 可以理解成是一个循环，一旦在线程中启动了 RunLoop，线程就会一直处理这个循环中，有事件需要处理时线程会自动处理这些事件，没有事件处理时，线程会自动休眠。一般情况下，中小项目基本不会直接使用 RunLoop，可能会间接用到它，例如一些三方库里可能会用到；大项目可能会直接使用 RunLoop，例如监控主线程卡顿、延迟或集中处理一些事件等等，主要都是用来做一些性能优化方面的操作。</p>
</li>
<li><p>请描述一下 Runloop 内部的实现逻辑。</p>
<p> 简单的说，RunLoop 内部就是一个 while 循环，内部会判断是否有事情可做，如果有的话就处理相关事情，如果没有的话就调用相关内核函数进行休眠。详细实现逻辑请跳转至 <a href="#anchor1">RunLoop源码分析</a> 阅读。</p>
</li>
<li><p>自动释放池和 RunLoop 的联系。</p>
<p> 请跳转至 <a href="#anchor2">AutoreleasePool 和 RunLoop 的关系</a> 阅读。</p>
</li>
<li><p>RunLoop 和线程的关系。</p>
<p> 点击跳转到 <a href="#anchor3">RunLoop与线程的关系</a> 段落。</p>
</li>
<li><p>RunLoop 和 NSTimer 的关系？</p>
<p> 请跳转至 <a href="#anchor4">NSTimer 和 RunLoop 的关系</a> 阅读。</p>
</li>
<li><p>RunLoop 中的 Mode 是什么?</p>
<p> 请跳转至 <a href="#anchor5">RunLoopMode</a> 阅读。</p>
</li>
</ol>
</div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">布多</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://internetwei.github.io/2024/04/28/iOS/深入理解iOS中的RunLoop/深入理解iOS中的RunLoop/">https://internetwei.github.io/2024/04/28/iOS/深入理解iOS中的RunLoop/深入理解iOS中的RunLoop/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://internetwei.github.io">布多的博客</a>！</span></div></div><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/ObjC/">ObjC</a><a class="post-meta__tags" href="/tags/RunLoop/">RunLoop</a></div><nav id="pagination"><div class="prev-post pull-left"><a href="/2024/12/11/iOS/2024%20%E5%86%8D%E6%8E%A2ObjC-Category%EF%BC%9A%E5%8A%A8%E6%80%81%E7%89%B9%E6%80%A7%E4%B8%8E%E8%BF%90%E8%A1%8C%E6%97%B6%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%9E%81%E8%87%B4%E4%B9%8B%E7%BE%8E/2024%20%E5%86%8D%E6%8E%A2ObjC-Category%EF%BC%9A%E5%8A%A8%E6%80%81%E7%89%B9%E6%80%A7%E4%B8%8E%E8%BF%90%E8%A1%8C%E6%97%B6%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%9E%81%E8%87%B4%E4%B9%8B%E7%BE%8E/"><i class="fa fa-chevron-left">  </i><span>2024 再探ObjC-Category：动态特性与运行时实现的极致之美</span></a></div><div class="next-post pull-right"><a href="/2023/10/21/iOS/%E6%8F%AD%E5%BC%80%20iOS%20%E4%B8%AD%20weak%20%E6%8C%87%E9%92%88%E7%9A%84%E7%A5%9E%E7%A7%98%E9%9D%A2%E7%BA%B1%EF%BC%9A%E4%BB%8E%E5%8E%9F%E7%90%86%E5%88%B0%E5%AE%9E%E8%B7%B5/%E6%8F%AD%E5%BC%80%20iOS%20%E4%B8%AD%20weak%20%E6%8C%87%E9%92%88%E7%9A%84%E7%A5%9E%E7%A7%98%E9%9D%A2%E7%BA%B1%EF%BC%9A%E4%BB%8E%E5%8E%9F%E7%90%86%E5%88%B0%E5%AE%9E%E8%B7%B5/"><span>揭开 iOS 中 weak 指针的神秘面纱：从原理到实践</span><i class="fa fa-chevron-right"></i></a></div></nav><div id="lv-container" data-id="city" data-uid="MTAyMC81MzU5Ny8zMDA3MA=="><script>(function(d, s) {
    var j, e = d.getElementsByTagName(s)[0];
    if (typeof LivereTower === 'function') { return; }
    j = d.createElement(s);
    j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
    j.async = true;
    e.parentNode.insertBefore(j, e);
})(document, 'script');</script></div></div></div><footer class="footer-bg" style="background-image: url(images/backgroundImage.jpg)"><div class="layout" id="footer"><div class="copyright">&copy;2021 - 2025 By 布多</div><div class="framework-info"><span>驱动 - </span><a target="_blank" rel="noopener" href="http://hexo.io"><span>Hexo</span></a><span class="footer-separator">|</span><span>主题 - </span><a target="_blank" rel="noopener" href="https://github.com/Molunerfinn/hexo-theme-melody"><span>Melody</span></a></div><div class="footer_custom_text">hitokoto</div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv"><i class="fa fa-file-o"></i><span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/lib/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.9.1"></script><script src="/js/fancybox.js?version=1.9.1"></script><script src="/js/sidebar.js?version=1.9.1"></script><script src="/js/copy.js?version=1.9.1"></script><script src="/js/fireworks.js?version=1.9.1"></script><script src="/js/transition.js?version=1.9.1"></script><script src="/js/scroll.js?version=1.9.1"></script><script src="/js/head.js?version=1.9.1"></script><script src="/js/search/local-search.js"></script><script>if(/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
  $('#top-container').addClass('is-mobile')
}</script><div class="search-dialog" id="local-search"><div class="search-dialog__title" id="local-search-title">本地搜索</div><div id="local-input-panel"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章"></div></div></div><hr><div id="local-search-results"><div id="local-hits"></div><div id="local-stats"><div class="local-search-stats__hr" id="hr"><span>由</span> <a target="_blank" rel="noopener" href="https://github.com/wzpan/hexo-generator-search" style="color:#49B1F5;">hexo-generator-search</a>
 <span>提供支持</span></div></div></div><span class="search-close-button"><i class="fa fa-times"></i></span></div><div class="search-mask"></div></body></html>